﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Bridge.Base.Utilities;
using Bridge.UI.Utilities;
using Pathfinder.Base.Constructs;
using System.Collections.Specialized;

namespace Pathfinder.Base.Importer
{
    /// <summary>
    /// Importing for the Main part of the statblock.
    /// </summary>
    public static class MainStatblockImporter
    {
        /// <summary>
        /// Finds the name of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>Name found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input is empty.</exception>
        public static string FindName(string input)
        {
            input.ThrowIfNullOrEmpty("input");

            return FindName(input, NameRegex);
        }

        /// <summary>
        /// Finds the name of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>Name found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static string FindName(string input, string regexExpression)
        {
            input.ThrowIfNullOrEmpty("input");
            regexExpression.ThrowIfNullOrEmpty("regexExpression");

            string name = input.FindFirstMatch(@"^.+?(?=CR)");
            return name == null ? null : name.Trim();
        }

        /// <summary>
        /// Finds the CR of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>CR found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input is empty.</exception>
        public static string FindCR(string input)
        {
            input.ThrowIfNullOrEmpty("input");

            return FindCR(input, CRRegex);
        }

        /// <summary>
        /// Finds the CR of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <param name="regexExpression">Regular expression to find.</param>
        /// <returns>CR found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static string FindCR(string input, string regexExpression)
        {
            input.ThrowIfNullOrEmpty("input");
            regexExpression.ThrowIfNullOrEmpty("regexExpression");

            return input.FindFirstMatch(regexExpression);
        }


        /// <summary>
        /// Finds the Alignment of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>Alignment found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input is empty.</exception>
        public static Alignment? FindAlignment(string input)
        {
            return FindAlignment(input, GetAlignmentRegex());
        }

        /// <summary>
        /// Finds the Alignment of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <param name="regexExpression">Regular expression to use to find the alignment.</param>
        /// <returns>Alignment found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static Alignment? FindAlignment(string input, string regexExpression)
        {
            Alignment? foundAlignment = null;
            string statBlockFoundAlignment = input.FindFirstMatch(regexExpression) ?? "N";
            foreach (Alignment alignment in Enum.GetValues(typeof(Alignment)))
            {
                if (alignment.GetUIDisplayAttribute().ConversionName.Equals(statBlockFoundAlignment, StringComparison.OrdinalIgnoreCase))
                {
                    foundAlignment = alignment;
                }
            }
            return foundAlignment;
        }

        /// <summary>
        /// Finds the size of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>SizeType found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input is empty.</exception>
        public static SizeType? FindSize(string input)
        {
            input.ThrowIfNullOrEmpty("input");

            string alignments =
                ((Alignment[])Enum.GetValues(typeof(Alignment))).Combine<Alignment>(
                    item => string.Format("(?<={0} )", item.GetUIDisplayAttribute().ConversionName),
                    "|");

            string sizes = ((SizeType[])Enum.GetValues(typeof(SizeType))).Combine<SizeType>("|");

            return FindSize(input, string.Format("({0})({1})", alignments, sizes));
        }

        /// <summary>
        /// Finds the size of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <param name="regexExpression">Regular expression to use to find the size.</param>
        /// <returns>SizeType found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static SizeType? FindSize(string input, string regexExpression)
        {
            input.ThrowIfNullOrEmpty("input");
            regexExpression.ThrowIfNullOrEmpty("regexExpression");

            string size = input.FindFirstMatch(regexExpression);
            if (size != null)
            {
                SizeType sizeType;
                if (size.TryGetEnumValue<SizeType>(out sizeType))
                {
                    return sizeType;
                }
            }
            return null;
        }

        /// <summary>
        /// Finds the creature type of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>CreatureType found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input is empty.</exception>
        public static CreatureType? FindCreatureType(string input)
        {
            input.ThrowIfNullOrEmpty("input");

            string sizes =
                ((SizeType[])Enum.GetValues(typeof(SizeType))).Combine<SizeType>(
                    item => string.Format("(?<={0} )", item),
                    "|");

            string creatureTypes =
                ((CreatureType[])Enum.GetValues(typeof(CreatureType))).Combine<CreatureType>(
                    item =>
                    {
                        UIDisplayAttribute attribute = item.GetUIDisplayAttribute();
                        return attribute == null ? item.ToString() : attribute.ConversionName;
                    },
                    "|");

            return FindCreatureType(input, string.Format("({0})({1})", sizes, creatureTypes));
        }

        /// <summary>
        /// Finds the creature type of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <param name="regexExpression">Regular expression to use.</param>
        /// <returns>CreatureType found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static CreatureType? FindCreatureType(string input, string regexExpression)
        {
            string creatureType = input.FindFirstMatch(regexExpression);
            if (creatureType != null)
            {
                CreatureType creatureTypeEnum;
                if (creatureType.Replace(' ', '_').TryGetEnumValue<CreatureType>(out creatureTypeEnum))
                {
                    return creatureTypeEnum;
                }
            }
            return null;
        }

        /// <summary>
        /// Finds the creature type of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>CreatureType found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input is empty.</exception>
        public static IEnumerable<SubType> FindSubTypes(string input)
        {
            input.ThrowIfNullOrEmpty("input");

            string subTypeListRegex =
                string.Format("({0})",
                    new List<CreatureType>((CreatureType[])Enum.GetValues(typeof(CreatureType))).Combine<CreatureType>(creatureType =>
                    {
                        string format = @"(?<={0} \()";
                        string result = creatureType.ToString();
                        if (creatureType.GetUIDisplayAttribute() != null)
                        {
                            result = creatureType.GetUIDisplayAttribute().ConversionName;
                        }
                        return string.Format(format, result);
                    }, "|"));

            string subTypeRegex = new List<SubType>((SubType[])Enum.GetValues(typeof(SubType))).Combine<SubType>(subType => string.Format("({0})", subType), "|");
            subTypeListRegex += string.Format("({0})", subTypeRegex) + @"(?=\))";

            return FindSubTypes(input, subTypeListRegex, subTypeRegex);
        }

        /// <summary>
        /// Finds the creature type of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <param name="subTypeInfoRegex">Regex to use to search in the list.</param>
        /// <param name="subTypeListRegex">Regex to use to search for the list.</param>
        /// <returns>CreatureType found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static IEnumerable<SubType> FindSubTypes(string input, string subTypeListRegex, string subTypeInfoRegex)
        {
            input.ThrowIfNullOrEmpty("input");
            subTypeListRegex.ThrowIfNullOrEmpty("subTypeListRegex");
            subTypeInfoRegex.ThrowIfNullOrEmpty("subTypeInfoRegex");

            string subTypeList = input.FindFirstMatch(subTypeListRegex);
            StringCollection subTypeInfo = string.IsNullOrEmpty(subTypeList) ? null : subTypeList.FindAllMatches(subTypeInfoRegex);

            if (subTypeInfo != null)
            {
                List<SubType> subTypes = new List<SubType>();
                foreach (string subType in subTypeInfo)
                {
                    foreach (SubType subTypeEnum in Enum.GetValues(typeof(SubType)))
                    {
                        if (subTypeEnum.ToString().Equals(subType.Trim(), StringComparison.OrdinalIgnoreCase))
                        {
                            subTypes.Add(subTypeEnum);
                        }
                    }
                }
                return subTypes;
            }
            return null;
        }

        /// <summary>
        /// Finds the initiative of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>CreatureType found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static int? FindInitiative(string input)
        {
            return FindInitiative(input, InitiativeRegex);
        }

        /// <summary>
        /// Finds the initiative of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <param name="regexExpression">Regex to use to search</param>
        /// <returns>CreatureType found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static int? FindInitiative(string input, string regexExpression)
        {
            string initiative = input.FindFirstMatch(regexExpression);
            if (initiative != null)
            {
                int initNumber = int.Parse(initiative.FindFirstMatch(@"\d+"));
                bool isPlus = Regex.IsMatch(initiative, @"\+");
                int initTotal = initNumber * (isPlus ? 1 : -1);
                return initTotal;
            }
            return null;
        }

        /// <summary>
        /// Finds the sight of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>Dictionary of sights found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input is empty.</exception>
        public static Dictionary<SightType, int> FindSight(string input)
        {
            return FindSight(input, SightRegex);
        }

        /// <summary>
        /// Finds the sight of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <param name="regexExpression">Regex to use to search</param>
        /// <returns>Dictionary of sights found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static Dictionary<SightType, int> FindSight(string input, string regexExpression)
        {
            string sights = input.FindFirstMatch(regexExpression);
            if (sights != null)
            {
                Dictionary<SightType, int> sightTypes = new Dictionary<SightType, int>();
                bool addNormal = true;
                string blindsight = sights.FindFirstMatch(@"(?<=Blindsight )\d+");
                string darkvision = sights.FindFirstMatch(@"(?<=Darkvision )\d+");
                string lowlight = sights.FindFirstMatch(@"low\-light vision");
                string seeindarkness = sights.FindFirstMatch(@"see in darkness");
                string tremorsense = sights.FindFirstMatch(@"(?<=Tremorsense )\d+");
                string scent = sights.FindFirstMatch(@"scent");

                if (blindsight != null)
                {
                    addNormal = false;
                    sightTypes.Add(SightType.Blindsight, int.Parse(blindsight));
                }
                if (darkvision != null)
                {
                    addNormal = false;
                    sightTypes.Add(SightType.Darkvision, int.Parse(darkvision));
                }
                if (lowlight != null)
                {
                    addNormal = false;
                    sightTypes.Add(SightType.Lowlight, 1);
                }
                if (seeindarkness != null)
                {
                    addNormal = false;
                    sightTypes.Add(SightType.See_Darkness, 1);
                }
                if (tremorsense != null)
                {
                    sightTypes.Add(SightType.Tremorsense, int.Parse(tremorsense));
                }
                if (scent != null)
                {
                    sightTypes.Add(SightType.Scent, 1);
                }
                if (addNormal)
                {
                    sightTypes.Add(SightType.Normal, 1);
                }
                return sightTypes;
            }
            return null;
        }

        /// <summary>
        /// Finds the aura of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <returns>Aura found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input is empty.</exception>
        public static string FindAura(string input)
        {
            return FindAura(input, AuraRegex);
        }

        /// <summary>
        /// Finds the aura of the statblock.
        /// </summary>
        /// <param name="input">Full statblock to search.</param>
        /// <param name="regexExpression">Regex to use to search</param>
        /// <returns>Aura found; null if nothing is found.</returns>
        /// <exception cref="ArgumentNullException">Thrown if input or regexExpression is null.</exception>
        /// <exception cref="ArgumentException">Thrown if input or regexExpression is empty.</exception>
        public static string FindAura(string input, string regexExpression)
        {
            return input.FindFirstMatch(regexExpression);
        }

        /// <summary>
        /// Gets the default alignment regex string.
        /// </summary>
        /// <returns>Regular expression string for FindAlignment.</returns>
        public static string GetAlignmentRegex()
        {
            return string.Format(
                AlignmentRegex,
                ((Alignment[])Enum.GetValues(typeof(Alignment))).Combine<Alignment>(
                item =>
                {
                    UIDisplayAttribute attribute = item.GetUIDisplayAttribute();
                    string name = attribute == null ? item.ToString() : attribute.ConversionName;
                    return name;
                }, "|"));
        }

        private const string CRRegex = @"(?<=CR )[0-9/]+";
        private const string NameRegex = @"^.+?(?=CR)";
        private const string AlignmentRegex = @"(?<=\W)({0})(?= )";
        private const string InitiativeRegex = @"Init .\d+";
        private const string SightRegex = @"(?<=Senses ).+?((?=Perception)|(?=;)|(?=DEFENSE))";
        private const string AuraRegex = @"(?<=Aura ).+?(?=DEFENSE)";
    }
}
